﻿using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Interactivity;
using System.ComponentModel;

using WP7Contrib.View.Controls.Extensions;
using System.Windows.Controls;

namespace WP7Contrib.View.Controls.Behaviors
{
    /// <summary>
    /// Adding this behaviour to an element causes it to tilt when it is pressed.
    /// </summary>
    public class TiltBehaviour : Behavior<FrameworkElement>
    {
        #region fields

        /// <summary>
        /// The extent of the tilt action, the larger the number, the bigger the tilt
        /// </summary>
        private static double TiltAngleFactor = 4;

        /// <summary>
        /// The extent of the scaling action, the smaller the number, the greater the scaling.
        /// </summary>
        private static double ScaleFactor = 100;


        private PlaneProjection _projection = new PlaneProjection();

        private ScaleTransform _scale = new ScaleTransform();

        private TranslateTransform _translate = new TranslateTransform();

        #endregion

        #region DPs

        /// <summary>
        /// Gets or sets how pronounced the tilt effect is. A higher number means a greater tilt.
        /// This is a dependency property.
        /// </summary>
        [Description("Indicates how pronounced the tilt effect is. A higher number means a greater tilt")]
        [Category("Appearance")]
        public double TiltFactor
        {
            get { return (double)GetValue(TiltFactorProperty); }
            set { SetValue(TiltFactorProperty, value); }
        }

        /// <summary>
        /// Identifies the TiltFactor dependency property
        /// </summary>
        public static readonly DependencyProperty TiltFactorProperty =
            DependencyProperty.Register("TiltFactor", typeof(double), typeof(TiltBehaviour), new PropertyMetadata(2.0));

        #endregion

        #region Behaviour overrides

        protected override void OnAttached()
        {
            base.OnAttached();

            // add the required transformations
            var transGroup = new TransformGroup();
            transGroup.Children.Add(_scale);
            transGroup.Children.Add(_translate);

            AssociatedObject.Projection = _projection;
            AssociatedObject.RenderTransform = transGroup;
            AssociatedObject.RenderTransformOrigin = new Point(0.5, 0.5);

            // handle events from the associated object
            AssociatedObject.ManipulationStarted += AssociatedObject_ManipulationStarted;
            AssociatedObject.ManipulationCompleted += AssociatedObject_ManipulationCompleted;
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();

            AssociatedObject.ManipulationStarted -= AssociatedObject_ManipulationStarted;
            AssociatedObject.ManipulationCompleted -= AssociatedObject_ManipulationCompleted;

        }

        #endregion

        #region private

        private void AssociatedObject_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
        {
            var sb = new Storyboard();
            sb.Children.Add(CreateAnimation(null, 0, 0.1, "RotationY", _projection));
            sb.Children.Add(CreateAnimation(null, 0, 0.1, "RotationX", _projection));
            sb.Children.Add(CreateAnimation(null, 1, 0.1, "ScaleX", _scale));
            sb.Children.Add(CreateAnimation(null, 1, 0.1, "ScaleY", _scale));
            sb.Begin();

            _translate.Y = 0;
            _projection.LocalOffsetY = 0;
        }

        private void AssociatedObject_ManipulationStarted(object sender, ManipulationStartedEventArgs e)
        {
            FrameworkElement container = e.ManipulationContainer as FrameworkElement;
            var clickPosition = container.TransformToVisual(AssociatedObject).Transform(e.ManipulationOrigin);

            // find the maximum of width / height
            double maxDimension = Math.Max(AssociatedObject.ActualWidth, AssociatedObject.ActualHeight);

            // compute the normalised horizontal distance from the centre
            double distanceFromCenterX = AssociatedObject.ActualWidth / 2 - clickPosition.X;
            double normalisedDistanceX = 2 * distanceFromCenterX / maxDimension;

            // rotate around the Y axis 
            _projection.RotationY = normalisedDistanceX * TiltAngleFactor * TiltFactor;

            // compute the normalised vertical distance from the centre
            double distanceFromCenterY = AssociatedObject.ActualHeight / 2 - clickPosition.Y;
            double normalisedDistanceY = 2 * distanceFromCenterY / maxDimension;

            // rotate around the X axis, 
            _projection.RotationX = -normalisedDistanceY * TiltAngleFactor * TiltFactor;

            // find the distance to centre
            double distanceToCentre = Math.Sqrt(normalisedDistanceX * normalisedDistanceX
                + normalisedDistanceY * normalisedDistanceY);

            // scale accordingly
            double scaleVal = TiltFactor * (1 - distanceToCentre) / ScaleFactor;
            _scale.ScaleX = 1 - scaleVal;
            _scale.ScaleY = 1 - scaleVal;

            // offset the plane transform
            var rootElement = Application.Current.RootVisual as FrameworkElement;
            var relativeToCentre = (AssociatedObject.GetRelativePosition(rootElement).Y - rootElement.ActualHeight / 2) / 2;
            _translate.Y = -relativeToCentre;
            _projection.LocalOffsetY = +relativeToCentre;

        }

        internal static DoubleAnimation CreateAnimation(double? from, double? to, double duration, object targetProperty, DependencyObject target)
        {
            var db = new DoubleAnimation();
            db.To = to;
            db.From = from;
            db.EasingFunction = new SineEase();
            db.Duration = TimeSpan.FromSeconds(duration);
            Storyboard.SetTarget(db, target);
            if (targetProperty is string)
            {
                Storyboard.SetTargetProperty(db, new PropertyPath((string)targetProperty));
            }
            else
            {
                Storyboard.SetTargetProperty(db, new PropertyPath(targetProperty));
            }
            return db;
        }

        #endregion

    }
}
